﻿using System;
using System.Collections.Generic;
using System.Data.Entity.Validation;
using System.Linq;
using Jet.Dal.EF.Implementation;
using Jet.Dal.EF.Implementation.Contexts;
using Jet.Dal.EF.Tests.DataObjects;
using Jet.Dal.Exceptions;
using System.Data.Entity.Infrastructure;
using NUnit.Framework;
using Assert = NUnit.Framework.Assert;

namespace Jet.Dal.EF.Tests
{
	[TestFixture]
	public class EFUnitOfWorkTests
	{
		private class ValidationContext : DbContext1<Order>
		{
			public ValidationContext(string connectionString)
				: base(connectionString, true, true)
			{
			}

			protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
			{
				return new DbEntityValidationResult(entityEntry, new[]{
					new DbValidationError("CustomerID", "CustomerError"), 
					new DbValidationError("OrderID", "OrderError")});
			}
		}

		[Test]
		public void GetRepoTest()
		{
			using (var uow = new EFUnitOfWork(new DbContext1<Customer>(Constants.ConnectionString, true, true)))
			{
				var repo = uow.GetRepository<Customer>();
				Assert.IsNotNull(repo);
			}
		}

		[Test]
		public void SaveChangesGeneralTest()
		{
			Order order;
			int orderID;
			using (var uow = new EFUnitOfWork(new DbContext4<Customer, Employee, Shipper, Order>(Constants.ConnectionString, true, true)))
			{
				var customer = (from c in uow.GetRepository<Customer>().GetQuery() select c).First();
				var employee = (from c in uow.GetRepository<Employee>().GetQuery() select c).First();
				var shipper = (from c in uow.GetRepository<Shipper>().GetQuery() select c).First();
				order = uow.GetRepository<Order>().Create();
				order.CustomerID = customer.CustomerID;
				order.EmployeeID = employee.EmployeeID;
				order.ShipVia = shipper.ShipperID;
				order.OrderDate = DateTime.Now;
				order.ShippedDate = DateTime.Now;
				order.RequiredDate = DateTime.Now;
				order.Freight = (decimal)3.14;
				order.ShipAddress = "Red Square";
				order.ShipCity = "Moscow";
				order.ShipName = "Pu";
				order.ShipPostalCode = "1";
				order.ShipRegion = "Central";
				uow.GetRepository<Order>().Insert(order);
				uow.SaveChanges();
				orderID = order.OrderID;
			}
			using (var uow = new EFUnitOfWork(new DbContext1<Order>(Constants.ConnectionString, true, true)))
			{
				var newOrder = (from o in uow.GetRepository<Order>().GetQuery()
								where o.OrderID == orderID
								select o).First();
				Assert.AreEqual(order.CustomerID, newOrder.CustomerID);
				Assert.AreEqual(order.EmployeeID, newOrder.EmployeeID);
				// Date time truncate milliseconds when writes to db. So really dates are not equal.
				Assert.AreEqual(order.ShipVia, newOrder.ShipVia);
				// ReSharper disable SpecifyACultureInStringConversionExplicitly
				Assert.AreEqual(order.OrderDate.ToString(), newOrder.OrderDate.ToString());
				Assert.AreEqual(order.ShippedDate.ToString(), newOrder.ShippedDate.ToString());
				Assert.AreEqual(order.RequiredDate.ToString(), newOrder.RequiredDate.ToString());
				// ReSharper restore SpecifyACultureInStringConversionExplicitly
				Assert.AreEqual(order.Freight, newOrder.Freight);
				Assert.AreEqual(order.ShipAddress, newOrder.ShipAddress);
				Assert.AreEqual(order.ShipCity, newOrder.ShipCity);
				Assert.AreEqual(order.ShipName, newOrder.ShipName);
				Assert.AreEqual(order.ShipPostalCode, newOrder.ShipPostalCode);
				Assert.AreEqual(order.ShipRegion, newOrder.ShipRegion);
			}
		}

		[Test]
		public void SaveChangesExceptionsTest()
		{
			using (var uow = new EFUnitOfWork(new DbContext1<Order>(Constants.ConnectionString, true, true)))
			{
				var order = uow.GetRepository<Order>().Create();
				order.CustomerID = "12345678";
				uow.GetRepository<Order>().Insert(order);
				var local = uow;
				Assert.That(()=>local.SaveChanges(), Throws.TypeOf<DataUpdateException>());
			}
			using (var uow = new EFUnitOfWork(new DbContext1<Shipper>(Constants.ConnectionString, true, true)))
			{
				var shipper = uow.GetRepository<Shipper>().Create();
				shipper.ShipperID = -1;
				uow.GetRepository<Shipper>().Attach(shipper);
				shipper.CompanyName = "Marine";
				var local = uow;
				Assert.That(() => local.SaveChanges(), Throws.TypeOf<DataUpdateConcurrencyException>()); 
			}
			using (var uow = new EFUnitOfWork(new ValidationContext(Constants.ConnectionString)))
			{
				var order = uow.GetRepository<Order>().Create();
				order.OrderID = -1;
				uow.GetRepository<Order>().Attach(order);
				order.OrderDate = DateTime.Now;
				var local = uow;
				Assert.That(() => local.SaveChanges(), Throws.TypeOf<ValidationException>());
			}
		}

		[Test]
		public void CtorTest()
		{
			var uow = new EFUnitOfWork(new DbContext1<Order>(Constants.ConnectionString, true, true));
			Assert.IsNotNull(uow);
			Assert.That(() => new EFUnitOfWork(null), Throws.TypeOf<ArgumentNullException>());
		}

		[Test]
		public void LazyLoadingTest()
		{
			using (var uow = new EFUnitOfWork(new DbContext2<Order, Customer>(Constants.ConnectionString, true, true)))
			{
				var order = (from o in uow.GetRepository<Order>().GetQuery()
							 where o.EmployeeID > 0
							 select o).First();
				var employee = order.Employee;
				Assert.IsNotNull(employee);
			}

			using (var uow = new EFUnitOfWork(new DbContext2<Order, Customer>(Constants.ConnectionString, false, false)))
			{
				var order = (from o in uow.GetRepository<Order>().GetQuery()
							 where o.EmployeeID > 0
							 select o).First();
				var employee = order.Employee;
				Assert.IsNull(employee);
			}
		}
	}
}
